#include "Connection.h"
#include <iostream>
#include "Vector.h"
#include "BodyInfo.h"
#include <vector>
#include "Renderer.h"

#pragma warning (disable : 4482)

void	Connection::start(SOCKET s, unsigned int id)
{
	m_socket = s;
	
	// Create version identifier
	WORD wVersionRequested = MAKEWORD( 2, 0 );

	// Startup windows sockets
	WSADATA wsaData;
	if ( WSAStartup( wVersionRequested, &wsaData )) {
		std::cerr << "Socket initialisation failed" << std::endl;
	}


	init();

	m_hSend = (HANDLE)_beginthreadex(NULL, 1024*1024, threadSend, 
										this, 0, NULL);

	m_hReceive = (HANDLE)_beginthreadex(NULL, 1024*1024, threadReceive, 
										this, 0, NULL);
}

int		Connection::init()
{

	closesocket(m_socket);

	int tcpPort;
	sockaddr_in serverAddr;
	
	bool foundServer = false;
	while (!foundServer) {
		foundServer = findServer(tcpPort, serverAddr);
	}
	
	// ------------------------------------------
	// Create socket data space
	sockaddr_in tcpAddr;
	tcpAddr.sin_family = AF_INET;
	tcpAddr.sin_port = htons(tcpPort);	// port sent from server
	tcpAddr.sin_addr.S_un.S_addr = serverAddr.sin_addr.S_un.S_addr; // UDP recvfrom() filled serverAddr structure

	// Create transfer socket
	m_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (m_socket==INVALID_SOCKET) {
		std::cerr << "Create socket failed" << std::endl;
	} 

	std::cout << "Attempting to make TCP connection" << std::endl;

	if (connect(m_socket, (sockaddr *)&tcpAddr, sizeof(tcpAddr))==SOCKET_ERROR) {
		std::cerr << "Connect to peer failed with " << WSAGetLastError() << std::endl;
	}

	std::cout << "Established TCP connection with server" << std::endl;

	mConnected = true;

	return 0;
}

void	Connection::waitForTermination()
{
	WaitForSingleObject(m_hSend, INFINITE);
	WaitForSingleObject(m_hReceive, INFINITE);
	CloseHandle(m_hSend);
	CloseHandle(m_hReceive);
	m_hSend = m_hReceive = 0;
}

struct	ClientHeader
{
	int	type;
	int bytes;
};

void	Connection::reconnect()
{
	static bool called = false;

	Sleep(10000);

	if (!called)
		called = true;
	else
		return;

	init();
}

bool	Connection::updateServer()
{
	char n[1] = { 'N' };

	// check if airjet is active if yes add a new message
	if (m_renderer->airjetActive()) {
		Message msg;
		msg.header.type = MsgType::MSG_NEWAIRJET;
		msg.header.length = 2 * sizeof(Vector);
		
		Vector airjet[2];
		airjet[0] = *(m_renderer->getAirjetStart());
		airjet[1] = *(m_renderer->getAirjetEnd());
		msg.data = (void*)&airjet;
		m_msgQueue.push_back(msg);
	}

	for (unsigned int i=0; i<m_msgQueue.size(); i++)
	{
		Message msg = m_msgQueue[i];
		
		if (msg.header.length == 0) { // server expects a header/message seq. 
			msg.data = n;
			msg.header.length = sizeof(char);
		}

		Vector v = *((Vector*)msg.data);

		// send message header
		if (send(m_socket, (char*)&msg.header, sizeof(msg.header), 0) == SOCKET_ERROR) {
			return false;
			std::cerr << "Send failed with " << WSAGetLastError() << std::endl;
		}

		// send message data
		if (send(m_socket, (char*)msg.data, msg.header.length, 0) == SOCKET_ERROR) {
			return false;
			std::cerr << "Send failed with " << WSAGetLastError() << std::endl;
		}
	}

	m_msgQueue.clear(); // lock!!!

	return true;
}

int		Connection::sendData()
{
	
	// 2rd processor
	HANDLE threadId = GetCurrentThread();
	DWORD_PTR processAffinityMask = 2;
	SetThreadAffinityMask(threadId, processAffinityMask);

	
	double freqNetwork = m_renderer->getNetworkFreq();
	double delta = 1.0f / (double)freqNetwork;

	double d0, d1, elapsedTime=0;
	int counter = 0;
	double timer = 0;
	double currentTime = 0;
	double avgElapsedTime = 0.0;
	double baseTime = getTime();

	startTimer();


	while (!m_bTerminate)
	{
		
		if (!mConnected) {
			SwitchToThread();
		}


		currentTime = getTime() - baseTime;
			
		if (currentTime >= delta*counter) 
		{
			d0 = getTime();
				mConnected = updateServer();
			d1 = getTime();
	
			elapsedTime = d1-d0;
			counter++;

			// Things to be checked after execution of each loop ------------
		}

		// sleep remaining time
		if (elapsedTime < delta) { } 
		else {
			//std::cout << "elapsedTime > delta" << endl;		
		}
		
		avgElapsedTime += elapsedTime;
		timer += getDeltaTime();

		// Things to do per Second -----------------------------
		if (timer >= 1.0) {
			timer = 0.0;

			// average elapsedTime
			avgElapsedTime /= (double)freqNetwork;
			// get new value for baseTime
			baseTime = getTime();


			// get current value for frequency and update delta
			freqNetwork = m_renderer->getNetworkFreq();
			delta = 1.0f / (double)freqNetwork;

			std::cout << "Network fps : " << counter << std::endl;

			// update average objects /s gui variable
			if (counter) {
				*m_objectsPerSecond = (float)mTotalObjects / (counter);
				mTotalObjects = 0;
			}

			avgElapsedTime = 0;
			counter = 0;
		}


		
	}

	return 0;
}
	
bool	Connection::receive(SOCKET socket, char* data, int size)
{
	int receivedBytes = 0;
	while(receivedBytes < size){
		int read = recv(socket, data+receivedBytes, size-receivedBytes, 0);
		if (read == SOCKET_ERROR)  {
			return false;
			std::cerr << "Receive failed with " << WSAGetLastError()  << std::endl;
		}
		receivedBytes += read;
	}
	return true;
}

int		Connection::receiveData()
{
	// 2rd processor
	HANDLE threadId = GetCurrentThread();
	DWORD_PTR processAffinityMask = 2;
	SetThreadAffinityMask(threadId, processAffinityMask);


	char buffer[100];
	ZeroMemory(buffer, sizeof(buffer));
	
	// init to 0
	*m_objectsReceived = 0;

	Message msg;

	const int statsBufferSize =  7 * sizeof(float);
	float statsBuffer[7];
	ZeroMemory(statsBuffer, sizeof(statsBuffer));

	while (!m_bTerminate)
	{

		if (!mConnected) {
			reconnect();
			SwitchToThread();
		}

		// read message header
		mConnected = receive(m_socket, (char*)&msg.header, sizeof(msg.header));
		
		if (!mConnected) continue;

		// read message data
		if (msg.header.type == MsgType::MSG_BodyInfo) {
			mConnected = receive(m_socket, (char*)m_rcvBuffer, msg.header.length);
			if (!mConnected) continue;

			*m_objectsReceived = msg.header.length / sizeof(BodyInfo);
			mTotalObjects += *m_objectsReceived;
		}
		else if (msg.header.type == MsgType::MSG_SPRINGINFO) {
			mConnected = receive(m_socket, (char*)m_rcvRopeBuffer, msg.header.length);
			*m_ropePoints = msg.header.length / sizeof(Vector);
		}
		else if (msg.header.type == MsgType::MSG_STATS) {
			// read stats
			mConnected = receive(m_socket, (char*)statsBuffer, msg.header.length);
			// stats buffer : totalobjects, airForce, elasticity, friction, freq,freq,freq
			m_renderer->setTotalObjects((int)statsBuffer[0]);
			m_renderer->setAirjetForce(statsBuffer[1]);
			m_renderer->setElasticity(statsBuffer[2]);
			m_renderer->setFriction(statsBuffer[3]);
			m_renderer->setGraphicsFreq(statsBuffer[4]);
			m_renderer->setNetworkFreq(statsBuffer[5]);
			m_renderer->setPhysicsFreq(statsBuffer[6]);
		}
		else if (msg.header.type == MsgType::MSG_SPRINGACTIVE) {
			int isActive;
			mConnected = receive(m_socket, (char*)&isActive, msg.header.length);
			if (isActive)
				m_renderer->bDrawSpring = true;
			else
				m_renderer->bDrawSpring = false;
		}
	
		
	}
	return 0;
}

void	Connection::terminate()
{ 
	m_bTerminate = true; 
}

bool	Connection::isTerminated() const 
{ 
	return m_bTerminate; 
}

void	Connection::setReceiveBuffer(void* data) 
{ 
	m_rcvBuffer = data; 
}

void	Connection::setRopeReceiveBuffer(void* data)
{ 
	m_rcvRopeBuffer = data; 
}

void	Connection::sendMessage(Message msg)
{ 
	m_msgQueue.push_back(msg); 
}

bool	Connection::findServer(int& tcpPort, sockaddr_in& serverAddr)
{
	static bool  initialised = false;

#define BROADCAST

	std::cout << "Attemp to find Server" << std::endl;

	// ----------- UDP broadcast ----------------
		// dst address
		sockaddr_in peer;
		peer.sin_family = AF_INET;
		peer.sin_port = htons(9171);	// server listens on 9171
#ifdef BROADCAST
		peer.sin_addr.S_un.S_addr = inet_addr( "255.255.255.255" ); // broadcast: 150.237.94.255 [!]
#else	
		peer.sin_addr.S_un.S_addr = inet_addr( "150.237.94.48" ); // broadcast: 150.237.94.255 [!]
#endif

		// local address
		sockaddr_in localaddr;
		localaddr.sin_family = AF_INET;
		localaddr.sin_port = htons(9172);	// port clients listens at 9172
		localaddr.sin_addr.S_un.S_addr = htonl( INADDR_ANY );

		// actual server addr - 
		//sockaddr_in serverAddr; - now a parameter
		int addrSize = sizeof(serverAddr);

		// Create transfer socket
		char port[4];
		SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
		if (s==INVALID_SOCKET) {
			std::cerr << "Create socket failed" << std::endl;
		}
		// bind socket (to local address)
		else if (bind(s, (sockaddr*)&localaddr, sizeof(localaddr))==SOCKET_ERROR) {
			std::cerr << "Client: UDP bind failed with " << WSAGetLastError()  << std::endl;
			closesocket(s); 
			return false;
		}
		// Set Broadcast option  ------------------------------------------
#ifdef BROADCAST
		DWORD broadcastPermission = 1;
		if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *) &broadcastPermission, sizeof(broadcastPermission)) != 0){
		   std::cerr << "Broadcast option failed with " << WSAGetLastError() << std::endl;
		   closesocket(s); 
		   return false;
		}
#endif

	// Broadcast Message ('$')
	if (sendto(s, "$", 1, 0, (sockaddr*)&peer, sizeof(peer)) == SOCKET_ERROR) {
		std::cout << "Client: UDP sent failed with : " << WSAGetLastError() << std::endl;
		closesocket(s); 
		return false;
	}
	// receive data
	
	if (recvfrom(s, port, 4, 0, (sockaddr*)&serverAddr, &addrSize) == SOCKET_ERROR) {
		std::cout << "Client: UDP receive failed" << WSAGetLastError() << std::endl;
		closesocket(s); 
		return false;
	}

	std::cout << "Received UDP response from server" << std::endl;

	closesocket(s); 
	// ------ End of UDP session ----------------


	tcpPort = *((int*)port);

	return true;
}

void	Connection::startTimer()
{
	LARGE_INTEGER li;
    QueryPerformanceFrequency(&li);
    freq = static_cast<double>(li.QuadPart);

    QueryPerformanceCounter(&li);
    tmr_start = li.QuadPart;
}

double	Connection::getDeltaTime()
{
	 LARGE_INTEGER li;

    QueryPerformanceCounter(&li);
    __int64 current = li.QuadPart;

    double dt = static_cast<double>(current - tmr_start) / freq;

    tmr_start = current;

    return dt;
}

double	Connection::getTime()
{
	 LARGE_INTEGER li;

    QueryPerformanceCounter(&li);
    __int64 current = li.QuadPart;

    return (double)(current/freq);
}

